package test.logic.nfm;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;

import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import cfsmon.common.Event;
import cfsmon.common.Parameter;
import cfsmon.core.LogConfig;
import cfsmon.logic.nfm.INfm;
import cfsmon.logic.nfm.NfmInputValidator;
import cfsmon.logic.nfm.NfmState;
import cfsmon.logic.nfm.declParser.NfmDeclParser;
import cfsmon.logic.nfm.declParser.ParseException;

public class NfmDeclParser01 {

	ByteArrayInputStream in;
	
	@BeforeClass
	public static void classSetUp(){
		Logger.getRootLogger().removeAllAppenders();
		Logger.getRootLogger().addAppender(new ConsoleAppender(LogConfig.cfsmonLogConsoleLayout));
	}
	
/*	@Before
	public void setUp() throws Exception {
		 parser = new NfmDeclParser();
	}*/

	@After
	public void tearDown() throws Exception {
		in = null;
	}

	@Test(expected=ParseException.class)
	public void testEmptyString() throws ParseException{
		String input = "";
		in = new ByteArrayInputStream(input.getBytes());
		NfmDeclParser.parse(in);
	}

	@Test
	public void testEmptyFsm() throws ParseException{
		String input = "fsm:";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertFalse(NfmInputValidator.isValid(nfm));
	}
	
	@Test
	public void testMinimalFsm() throws ParseException{
		String input = "fsm: init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getEvents().isEmpty());
		assertNotNull(nfm.getStates().get("s0"));
		assertTrue(nfm.getInitialStates().contains(nfm.getStates().get("s0")));
		assertTrue(nfm.getFinalStates().contains(nfm.getStates().get("s0")));
		assertTrue(nfm.getParameters().isEmpty());
		assertTrue(nfm.getMaximumNumberOfMonitors() == 1);
	}
	
	@Test
	public void testInvalidNumber01() throws ParseException{
		String input = "fsm(0): init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getMaximumNumberOfMonitors() == 1);
	}
	
	@Test
	public void testInvalidNumber02() throws ParseException{
		String input = "fsm(-17): init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getMaximumNumberOfMonitors() == 1);
	}
	
	@Test
	public void testValidNumber() throws ParseException{
		String input = "fsm(4): init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getMaximumNumberOfMonitors() == 4);
	}
	
	@Test
	public void testMultipleInitAndFinalStates() throws ParseException{
		String input = "fsm: init s0[] error s0[] init s1[] error s1[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getInitialStates().size() == 2);
		assertTrue(nfm.getFinalStates().size() == 2);
	}
	
	@Test(expected=ParseException.class)
	public void testImplicitDeclarations() throws ParseException{
		String input = "fsm: init s0[e0->s1, default s3] error s2[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
	}
	
	@Test
	public void testTransition() throws ParseException{
		String input = "fsm: event e0() init s0[e0->s1] error s2[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		
		assertTrue(nfm.getStates().containsKey("s0"));
		NfmState from = nfm.getStates().get("s0");
		
		assertTrue(nfm.getEvents().containsKey("e0"));
		Event e = nfm.getEvents().get("e0");
		
		assertTrue(nfm.getStates().containsKey("s1"));
		NfmState to = nfm.getStates().get("s1");
		
		assertTrue(from.getTransitions().get(e).size() == 1);
		assertTrue(from.getTransitions().get(e).contains(to));
	}
	
	@Test
	public void testIndetTransition() throws ParseException{
		String input = "fsm: event e0() init s0[e0->s1, e0->s2] error s2[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		NfmState from = nfm.getStates().get("s0");
		assertNotNull(from);
		Event e = nfm.getEvents().get("e0");
		assertNotNull(e);
		NfmState to1 = nfm.getStates().get("s1");
		assertNotNull(to1);
		NfmState to2 = nfm.getStates().get("s2");
		assertNotNull(to2);
		assertTrue(from.getTransitions().get(e).size() == 2);
		assertTrue(from.getTransitions().get(e).contains(to1));
		assertTrue(from.getTransitions().get(e).contains(to2));
	}
	
	@Test
	public void testImplicitDefaultTransition() throws ParseException{
		String input = "fsm: init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		NfmState s = nfm.getStates().get("s0");
		assertTrue(s.getDefaultTransition().size() == 1);
		assertTrue(s.getDefaultTransition().contains(s));
	}
	
	@Test
	public void testExplicitDefaultTransition() throws ParseException{
		String input = "fsm: init s0[default s1, default s2] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		NfmState from = nfm.getStates().get("s0");
		NfmState to1 = nfm.getStates().get("s1");
		NfmState to2 = nfm.getStates().get("s2");
		assertTrue(from.getDefaultTransition().size() == 2);
		assertTrue(from.getDefaultTransition().contains(to1));
		assertTrue(from.getDefaultTransition().contains(to2));
	}
	
	@Test(expected=ParseException.class)
	public void testIllegalExplicitEventDeclaration() throws ParseException{
		String input = "fsm: event e0 init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		NfmDeclParser.parse(in);
		
	}
	
	@Test
	public void testExplicitEventDeclaration() throws ParseException{
		String input = "fsm: event e0() init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		Event e0 = nfm.getEvents().get("e0");
		assertNotNull(e0);
		assertTrue(e0.parameters.isEmpty());
	}
	
	@Test(expected=ParseException.class)
	public void testUseEventBeforeDeclaration() throws ParseException{
		String input = "fsm: init s0[e0->s1] error s1[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		NfmDeclParser.parse(in);
	}
	
	@Test
	public void testParameterCoverage() throws ParseException{
		String input = "fsm: event e0(int i, String s, long l) event e1(char c, int i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		for (Event e : nfm.getEvents().values()){
			assertTrue(nfm.getParameters().values().containsAll(e.parameters));
		}
	}
	
	@Test
	public void testParameterCorrectness() throws ParseException{
		String input = "fsm: event e0(int i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		Event e0 = nfm.getEvents().get("e0");
		assertNotNull(e0);
		assertTrue(e0.parameters.size() == 1);
		Parameter p = e0.parameters.get(0);
		assertNotNull(p);
		assertTrue(p.type.equals("int"));
		assertTrue(p.name.equals("i"));
	}
	
	@Test
	public void testCommonParameter() throws ParseException{
		String input = "fsm: event e0(int i) event e1(int i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getParameters().values().size() == 1);
	}
	
	@Test
	public void testPointerAndArrayTypeEquivalence() throws ParseException{
		String input = "fsm: event e0(int *i) event e1(int i[]) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		INfm nfm = NfmDeclParser.parse(in);
		assertNotNull(nfm);
		assertTrue(nfm.getParameters().values().size() == 1);
	}
	
	@Test(expected=ParseException.class)
	public void testIncompatibleParameterTypes01() throws ParseException{
		// incompatible double declaration
		String input = "fsm: event e0(int i) event e1(long i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		NfmDeclParser.parse(in);
	}
	
	@Test(expected=ParseException.class)
	public void testIncompatibleParameterTypes02() throws ParseException{
		// incompatible double declaration
		String input = "fsm: event e0(int *i) event e1(int i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		NfmDeclParser.parse(in);
	}
	
	@Test(expected=ParseException.class)
	public void testIncompatibleParameterTypes03() throws ParseException{
		// incompatible double declaration
		String input = "fsm: event e0(int i) event e1(int *i) init s0[] error s0[]";
		in = new ByteArrayInputStream(input.getBytes());
		
		NfmDeclParser.parse(in);
	}

}
